package game;

import appstate.AIAppState;
import com.bulletphysics.collision.dispatch.CollisionObject;
import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.BulletAppState.ThreadingType;
import com.jme3.bullet.collision.PhysicsCollisionObject;
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.joints.PhysicsJoint;
import com.jme3.bullet.joints.Point2PointJoint;
import com.jme3.font.BitmapText;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.CameraControl;
import com.jme3.scene.control.CameraControl.ControlDirection;
import com.jme3.scene.shape.Box;
import com.jme3.system.AppSettings;
import control.character.MyPlayerControl;
import game.constant.Constants;
import game.constant.DefaultValues;
import game.creator.CharacterCreator;
import game.creator.SpatialCreator;
import game.creator.WorldCreator;
import game.gameobject.character.CharacterSettings;
import game.gameobject.character.CharacterStatus;
import game.gameobject.misc.Rope;
import handler.InputHandler;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Main extends SimpleApplication {

    public static void main(String[] args) {
        Main app = new Main();
	app.preStart();
        app.start();
    }

    protected void preStart() {
        Logger.getLogger("").setLevel(Level.INFO);
	AppSettings cfg = new AppSettings(true);
	cfg.setFrameRate(60); // set to less than or equal screen refresh rate
	//cfg.setVSync(true);   // prevents page tearing - destroying my framerate?
	//cfg.setFrequency(60); // set to screen refresh rate
	cfg.setResolution(640, 480);
	cfg.setFullscreen(false);
	//cfg.setSamples(2);    // anti-aliasing
	cfg.setTitle("My Game"); // branding: window name
	//try {
	  // Branding: window icon
	  //cfg.setIcons(new BufferedImage[]{ImageIO.read(new File("assets/Interface/icon.gif"))});
	//} catch (IOException ex) {
	  //Logger.getLogger(Main.class.getName()).log(Level.SEVERE, "Icon missing.", ex);
	//}
	// branding: load splashscreen from assets
	cfg.setSettingsDialogImage("Interface/MySplashscreen.png");
	setShowSettings(false);
	setSettings(cfg);
    }

    @Override
    public void simpleInitApp() {
	initDataStore();
	createWorld();
    }

    protected void initDataStore() {
	DataStore.assetManager = assetManager;

	initAppStates();
	initHubNodes();
	initPlayer();
	initGui();
	initHandlers();
    }

    protected void initAppStates() {
	BulletAppState bulletAppState = new BulletAppState();
	bulletAppState.setThreadingType(ThreadingType.PARALLEL);
        stateManager.attach(bulletAppState);
        bulletAppState.getPhysicsSpace().enableDebug(assetManager);

	AIAppState aiAppState = new AIAppState();
	stateManager.attach(aiAppState);

	DataStore.aiAppState = aiAppState;
	DataStore.bulletAppState = bulletAppState;
    }

    protected void initHubNodes() {
	Node physicsHubNode = new Node("physicsHubNode");
	rootNode.attachChild(physicsHubNode);
	Node worldHubNode = new Node("worldHubNode");
	rootNode.attachChild(worldHubNode);
	Node characterHubNode = new Node("characterHubNode");
	rootNode.attachChild(characterHubNode);

	DataStore.rootNode = rootNode;
	DataStore.physicsHubNode = physicsHubNode;
	DataStore.worldHubNode = worldHubNode;
	DataStore.characterHubNode = characterHubNode;
    }

    protected void initPlayer() {
	flyCam.setMoveSpeed(15f); //just for debugging purposes
        
        //TODO change near frustum/clipping plane

	//load player settings
	CharacterSettings playerSettings = new CharacterSettings();
	playerSettings.setHeight(DefaultValues.PLAYER_HEIGHT);
	playerSettings.setRadius(DefaultValues.PLAYER_RADIUS);
	playerSettings.setMass(DefaultValues.PLAYER_MASS);
	playerSettings.setStepHeight(DefaultValues.PLAYER_STEP_HEIGHT);
	playerSettings.setWalkSpeed(DefaultValues.PLAYER_WALK_SPEED);
        playerSettings.setRunSpeed(DefaultValues.PLAYER_RUN_SPEED);
	playerSettings.setSprintSpeed(DefaultValues.PLAYER_SPRINT_SPEED);

	//create player node
	Geometry player = new Geometry("me", new Box(0.381f, 0.381f, 0.381f));
	player.setUserData(Constants.CHARACTER_SETTINGS_OBJ, playerSettings);
	Material playerMaterial = new Material(DataStore.assetManager, "Common/MatDefs/Light/Lighting.j3md");
	playerMaterial.setBoolean("UseMaterialColors",true);
	playerMaterial.setColor("Diffuse",  ColorRGBA.White);
        playerMaterial.setColor("Specular", ColorRGBA.White);
	player.setMaterial(playerMaterial);
	player.setLocalTranslation(new Vector3f(0f, 4f, 6f));
	DataStore.characterHubNode.attachChild(player);

	//add player cam control
	CameraControl playerCamControl = new CameraControl(cam, ControlDirection.SpatialToCamera);
	playerCamControl.setEnabled(false);
	player.addControl(playerCamControl);

	//add player control
	CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(playerSettings.getRadius(), playerSettings.getHeight(), 1);
	MyPlayerControl playerControl = new MyPlayerControl(capsuleShape, playerSettings.getStepHeight());
	player.addControl(playerControl);
	DataStore.bulletAppState.getPhysicsSpace().add(player);

	//add ghost control to physics space
	DataStore.bulletAppState.getPhysicsSpace().add(playerControl.getGhostControl());

	//add animation control and set up channels - player needs a model before we can do this
	/*
	AnimControl animationControl = player.getControl(AnimControl.class);
	animationControl.addListener(playerControl);
	AnimChannel walkChannel = animationControl.createChannel();
	walkChannel.setAnim(Constants.CHARACTER_ANIM_STAND);
	AnimChannel attackChannelRight = animationControl.createChannel();
	attackChannelRight.addBone(animationControl.getSkeleton().getBone("uparm.right"));
	attackChannelRight.addBone(animationControl.getSkeleton().getBone("arm.right"));
	attackChannelRight.addBone(animationControl.getSkeleton().getBone("hand.right"));
	attackChannelRight.setAnim(Constants.CHARACTER_ANIM_STAND);
	AnimChannel attackChannelLeft = animationControl.createChannel();
	attackChannelLeft.addBone(animationControl.getSkeleton().getBone("uparm.left"));
	attackChannelLeft.addBone(animationControl.getSkeleton().getBone("arm.left"));
	attackChannelLeft.addBone(animationControl.getSkeleton().getBone("hand.left"));
	attackChannelLeft.setAnim(Constants.CHARACTER_ANIM_STAND);
	 */

	//load player status
	CharacterStatus playerStatus = new CharacterStatus();
	playerStatus.setHitPoints(100);
	player.setUserData(Constants.CHARACTER_STATUS_OBJ, playerStatus);

	//load player current values
	player.setUserData(Constants.CHARACTER_CURRENT_SPEED, playerSettings.getWalkSpeed());
	
	DataStore.player = player;
	DataStore.cam = cam;
    }

    protected void initGui() {
	guiNode.detachAllChildren();
	guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
	
	initCrosshairs();
	initPlayerGui();

	DataStore.guiNode = guiNode;
    }

    protected void initCrosshairs() {
	BitmapText ch = new BitmapText(guiFont);
	ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
	ch.setText("+");        // fake crosshairs :)
	ch.setLocalTranslation( // center
	  settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,
	  settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
	guiNode.attachChild(ch);
    }

    protected void initPlayerGui() {
	BitmapText hp = new BitmapText(guiFont);
	hp.setName(Constants.GUI_HIT_POINTS);
	CharacterStatus playerStatus = DataStore.player.getUserData(Constants.CHARACTER_STATUS_OBJ);
	hp.setText(playerStatus.getHitPoints().toString());
	hp.setLocalTranslation(0, settings.getHeight(), 0);
	guiNode.attachChild(hp);
    }

    protected void initHandlers() {
	InputHandler inputHandler = new InputHandler(inputManager, DataStore.player.getControl(MyPlayerControl.class));
	
	//CollisionHandler collisionHandler = new CollisionHandler();
	//DataStore.bulletAppState.getPhysicsSpace().addCollisionListener(collisionHandler);
    }

    public void createWorld() {
	//createAmbientLight(); //i like this, but it DESTROYS the gpu. find another way
        
	WorldCreator.initDefaultMaterials();
	
	Node room = WorldCreator.createRoom(Vector3f.ZERO, 10, 10, 10);
	DataStore.worldHubNode.attachChild(room);
	DataStore.bulletAppState.getPhysicsSpace().addAll(room);

        Geometry hookBlock = SpatialCreator.createBlock(new Vector3f(0.3f, 0.3f, 0.3f), new Vector3f(0f, 0f, 0f), 0f);
	Geometry hookBlock2 = SpatialCreator.createBlock(new Vector3f(0.3f, 0.3f, 0.3f), new Vector3f(1f, 0f, 0f), 1f);

        Rope rope = SpatialCreator.createRope(new Vector3f(0f, -0.3f, 0f), new Vector3f(1f, -0.3f, 0f), 2f);
	rope.move(0f, -rope.getLengthPerLink()/2, 0f);
        join(hookBlock, rope.getFirstLink(), new Vector3f(0f, -0.3f, 0f));
	join(hookBlock2, rope.getLastLink(), new Vector3f(1f, -0.3f, 0f));

	SpatialCreator.createTorch(new Vector3f(-3f, -4f, 0f));

	SpatialCreator.createCrate(new Vector3f(2f, -1f, -1f));
        
        CharacterCreator.createEnemy(new Vector3f(3f, 2f, 2f));

	Geometry hugeBlock = SpatialCreator.createBlock(new Vector3f(2f, 2f, 2f), new Vector3f(-0.5f, -2f, -3f), 0f);
	DataStore.worldHubNode.attachChild(hugeBlock);
    }

    /**
     * Creates six directional lights so things can be seen with no explicit lighting.
     * An AmbientLight does not actually work for this purpose.
     */
    public void createAmbientLight() {
	ColorRGBA ambientColor = new ColorRGBA(1f, 1f, 1f, 1f).multLocal(0.05f);
	
	DirectionalLight up = new DirectionalLight();
	up.setDirection(Vector3f.UNIT_Y);
	up.setColor(ambientColor);
	rootNode.addLight(up);
	DirectionalLight down = new DirectionalLight();
	down.setDirection(Vector3f.UNIT_Y.negate());
	down.setColor(ambientColor);
	rootNode.addLight(down);
	DirectionalLight right = new DirectionalLight();
	right.setDirection(Vector3f.UNIT_X);
	right.setColor(ambientColor);
	rootNode.addLight(right);
	DirectionalLight left = new DirectionalLight();
	left.setDirection(Vector3f.UNIT_X.negate());
	left.setColor(ambientColor);
	rootNode.addLight(left);
	DirectionalLight backward = new DirectionalLight();
	backward.setDirection(Vector3f.UNIT_Z);
	backward.setColor(ambientColor);
	rootNode.addLight(backward);
	DirectionalLight forward = new DirectionalLight();
	forward.setDirection(Vector3f.UNIT_Z.negate());
	forward.setColor(ambientColor);
	rootNode.addLight(forward);
    }

    public PhysicsJoint join(Spatial A, Spatial B, Vector3f connectionPoint) {
        Vector3f pivotA = A.worldToLocal(connectionPoint, new Vector3f());
        Vector3f pivotB = B.worldToLocal(connectionPoint, new Vector3f());
        Point2PointJoint joint = new Point2PointJoint(A.getControl(RigidBodyControl.class), B.getControl(RigidBodyControl.class), pivotA, pivotB);
	joint.setCollisionBetweenLinkedBodys(false);
	DataStore.bulletAppState.getPhysicsSpace().add(joint);
	return joint;
    }
    
    @Override
    public void simpleUpdate(float tpf) {
        
    }
}
